package cb

import (
	"fmt"
	"github.com/beego/beego/v2/core/logs"
	"github.com/couchbase/gocb/v2"
	"go1/db"
	"maps"
	"reflect"
	"strings"
)

type PageRequest struct {
	Obj      string              `json:"obj"`
	PageSize int                 `json:"pageSize"`
	Page     int                 `json:"page"`
	Fields   []string            `json:"fields"`
	Order    string              `json:"order"`
	Filter   []any               `json:"filter"`
	Optional map[string][]string `json:"optional"`
	Match    map[string][]string `json:"match"`
	isInfo   bool
}

type PageReply[T any] struct {
	PageSize   int `json:"pageSize"`
	Page       int `json:"page"`
	TotalPage  int `json:"totalPage"`
	TotalCount int `json:"totalCount"`
	List       []T `json:"list"`
}

type InfoReply[T any] struct {
	Row T
}

func (c *db.CbCrud[T]) ReplyList(pageRequest *PageRequest, opts *db.CrudOpts) PageReply[T] {
	var pageResp PageReply[T]
	c.SetTOpts(opts)
	var list []T
	tableName := GetTableName(opts)
	obj := pageRequest.Obj
	filter := pageRequest.Filter
	pageSize := pageRequest.PageSize
	page := pageRequest.Page
	order := pageRequest.Order
	tempObj := db.GetForeTempObj(obj)
	filterParams := db.BuildFilter[T](obj, filter, opts)
	filterSql := filterParams.Sql
	logs.Debug("The Final Filter Sql %s", filterSql)
	fieldsSql := db.BuildSelect(pageRequest.Obj, pageRequest.Fields, filterParams, opts)
	pageStr := BuildPageStr(page, pageSize)
	if order == "" {
		order = "\nORDER BY META().id"
	}
	sql := "\n"
	if pageRequest.isInfo {
		if len(filterSql) > 0 {
			sql += fmt.Sprintf("SELECT num[0].TotalCount, %s FROM %s %s\n%s %s %s", fieldsSql, tableName, obj, filterSql, order, pageStr)
		} else {
			sql += fmt.Sprintf("SELECT num[0].TotalCount, %s FROM %s %s %s %s", fieldsSql, tableName, obj, order, pageStr)
		}
	} else {
		if filterSql != "" {
			sql += fmt.Sprintf("SELECT num[0].TotalCount, %s FROM %s %s\n\tLET num = (SELECT COUNT(*) TotalCount FROM %s %s \n\t%s) \n%s %s %s", fieldsSql, tableName, obj, tableName, tempObj, filterSql, filterSql, order, pageStr)
		} else {
			sql += fmt.Sprintf("SELECT num[0].TotalCount, %s FROM %s %s\n\tLET num = (SELECT COUNT(*) TotalCount FROM %s t) %s %s", fieldsSql, tableName, obj, tableName, order, pageStr)
		}
	}
	rows := db.Run(sql, filterParams, opts)
	if pageRequest.isInfo {
		var t1 T
		_ = rows.One(&t1)
		list = append(list, t1)
	} else {
		for rows.Next() {
			var t1 T
			if pageResp.TotalCount == 0 {
				_ = rows.Row(&pageResp)
			}
			_ = rows.Row(&t1)
			list = append(list, t1)
		}
	}
	ma := pageRequest.Match
	maps.Copy(ma, pageRequest.Optional)
	c.SetForeignValues(obj, list, ma, filterParams, opts)
	pageResp.List = list
	if !pageRequest.isInfo {
		pageResp.Page = page
		pageResp.PageSize = pageSize
		pageResp.TotalPage = (pageResp.TotalCount + pageSize - 1) / pageSize
	}
	return pageResp
}

func (c *db.CbCrud[T]) ReplyInfo(pageRequest *PageRequest, opts *db.CrudOpts) InfoReply[T] {
	pageRequest.isInfo = true
	return InfoReply[T]{c.ReplyList(pageRequest, opts).List[0]}
}

func (c *db.CbCrud[T]) SetForeignValues(obj string, rows []T, match map[string][]string, filterParams *db.FilterParams, opts *db.CrudOpts) {
	if len(rows) == 0 {
		return
	}
	var t T
	r := reflect.TypeOf(t)
	gfkv, gfb := r.MethodByName("GetForeignKeyV")
	foreValues := map[string][]any{}
	forePks := map[string]string{}
	tableNames := map[string]string{}
	foreTypes := map[string]reflect.Type{}
	foreKeys := map[string]string{}
	a := make(map[string][]string)
	for foreObj := range match {
		fore := strings.Title(foreObj)
		oF, oFb := r.FieldByName(fore)
		if !oFb {
			continue
		}
		foreignS := db.GetTagIndex(oF, "foreignKey", 0)
		kk, _ := r.FieldByName(foreignS)
		logs.Debug("obj: %s, set foreign: %s foreignKey: %s", obj, foreObj, foreignS)
		foreKeys[foreObj] = kk.Name
		forePk := strings.Split(db.GetJsonTagIndex(oF.Type.Elem().Field(0), 0), ",")
		forePks[foreObj] = db.GetForeKey(forePk)
		tableNames[foreObj] = db.GetForeignTableName(oF.Type, opts)
		foreTypes[foreObj] = oF.Type.Elem()
		for _, row := range rows {
			v := reflect.ValueOf(row)
			if gfb {
				mA := []reflect.Value{reflect.ValueOf(t), reflect.ValueOf(foreignS), v, reflect.ValueOf(opts)}
				foreV := gfkv.Func.Call(mA)[0].Interface()
				foreValues[foreObj] = append(foreValues[foreObj], foreV)
				continue
			}
			foreV := v.FieldByName(foreignS).Interface()
			foreValues[foreObj] = append(foreValues[foreObj], foreV)
		}
	}
	foreResults := map[string]map[any]any{}
	logs.Debug("forePks ", forePks)
	for foreObj, foreKeys := range match {
		a[foreObj] = foreKeys
		selectStr := db.BuildOM(obj, a, filterParams, opts)
		pk := forePks[foreObj]
		filter := []any{foreValues[foreObj]}
		opts.Collection = tableNames[foreObj]
		tableName := GetTableName(opts)
		foreType := foreTypes[foreObj]
		sql := fmt.Sprintf("SELECT %s FROM %s %s WHERE %s IN $1 ", selectStr, tableName, foreObj, pk)
		mmap := GetAll2(sql, filter, foreType, opts)
		if len(mmap) != 0 {
			foreResults[foreObj] = mmap
		}
	}
	if len(foreResults) == 0 {
		return
	}
	for foreObj, foreResult := range foreResults {
		var setRowMethod reflect.Method
		_ = setRowMethod
		hasR := 0
		foreKey := foreKeys[foreObj]
		foresObj := strings.Title(foreObj)
		var fKF reflect.Value
		for index, row := range rows {
			rfv := reflect.ValueOf(&row).Elem()
			if gfb {
				mA := []reflect.Value{reflect.ValueOf(t), reflect.ValueOf(foreKey), rfv, reflect.ValueOf(opts)}
				fKF = gfkv.Func.Call(mA)[0]
			} else {
				fKF = rfv.FieldByName(foreKey)
			}
			logs.Debug("set foreign by foreignKey:", foreKey, ",valType:", fKF.Type(), ", value:", fKF.Interface())
			if v, ok := foreResult[fKF.Interface()]; ok {
				rfField := rfv.FieldByName(foresObj)
				nObj := reflect.New(rfField.Type().Elem())
				rfField.Set(nObj)
				nV := rfField.Elem()
				db.SetToRow(v.(map[string]any), rfField.Type().Elem(), nV)
				if hasR == 0 {
					md, mdb := rfField.Type().MethodByName("SetRow")
					setRowMethod = md
					if mdb {
						hasR = 1
					} else {
						hasR = -1
					}
				}
				if hasR > 0 {
					mA := []reflect.Value{nObj, reflect.ValueOf(opts)}
					setRowMethod.Func.Call(mA)
				}
				rows[index] = row
				continue
			}
			logs.Debug("not found fore match, foreObj: %s, foreKey: %s, %s", foreObj, foreKey, fKF.Interface(), fKF.Interface())
		}
	}
}

func GetAll2(sql string, params []any, ty reflect.Type, opts *db.CrudOpts) map[any]any {
	mm2 := map[any]any{}
	if len(params) == 0 {
		return mm2
	}
	f := ty.Field(0)
	pk := db.GetJsonTagIndex(f, 0)
	logs.Debug("getForeign Rows pk: %s, sql: %s, params: %v", pk, sql, params)
	rows, err := db.Conn.Query(sql, &gocb.QueryOptions{PositionalParameters: params, Adhoc: true})
	if err != nil {
		panic(fmt.Errorf("run sql failed, err: %v, sql: %s, params: %v", err, sql, params))
	}
	for rows.Next() {
		t := map[string]any{}
		_ = rows.Row(&t)
		kvalue := t[pk]
		mm2[kvalue] = t
	}
	return mm2
}
